home *** CD-ROM | disk | FTP | other *** search
/ Aminet 1 (Walnut Creek) / Aminet - June 1993 [Walnut Creek].iso / aminet / util / gnu / diff_2_1.lha / diff-2.1 / diff.c < prev    next >
C/C++ Source or Header  |  1993-02-04  |  26KB  |  993 lines

  1. /* GNU DIFF main routine.
  2.    Copyright (C) 1988, 1989, 1992 Free Software Foundation, Inc.
  3.  
  4. This file is part of GNU DIFF.
  5.  
  6. GNU DIFF is free software; you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation; either version 2, or (at your option)
  9. any later version.
  10.  
  11. GNU DIFF is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. GNU General Public License for more details.
  15.  
  16. You should have received a copy of the GNU General Public License
  17. along with GNU DIFF; see the file COPYING.  If not, write to
  18. the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
  19.  
  20. /* GNU DIFF was written by Mike Haertel, David Hayes,
  21.    Richard Stallman, Len Tower, and Paul Eggert.  */
  22.  
  23. #define GDIFF_MAIN
  24. #include "diff.h"
  25. #include "getopt.h"
  26. #include "fnmatch.h"
  27.  
  28. #ifndef DEFAULT_WIDTH
  29. #define DEFAULT_WIDTH 130
  30. #endif
  31.  
  32. #ifndef GUTTER_WIDTH_MINIMUM
  33. #define GUTTER_WIDTH_MINIMUM 3
  34. #endif
  35.  
  36. int diff_dirs ();
  37. int diff_2_files ();
  38.  
  39. static int compare_files ();
  40. static int specify_format ();
  41. static void add_regexp();
  42. static void specify_style ();
  43. static void usage ();
  44. #ifdef AMIGA
  45. static int fake_stat_result ();
  46. #endif /* AMIGA */
  47.  
  48. /* Nonzero for -r: if comparing two directories,
  49.    compare their common subdirectories recursively.  */
  50.  
  51. static int recursive;
  52.  
  53. /* For debugging: don't do discard_confusing_lines.  */
  54.  
  55. int no_discards;
  56.  
  57. /* Return a string containing the command options with which diff was invoked.
  58.    Spaces appear between what were separate ARGV-elements.
  59.    There is a space at the beginning but none at the end.
  60.    If there were no options, the result is an empty string.
  61.  
  62.    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
  63.    the length of that vector.  */
  64.  
  65. static char *
  66. option_list (optionvec, count)
  67.      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
  68.      int count;
  69. {
  70.   int i;
  71.   int length = 0;
  72.   char *result;
  73.  
  74.   for (i = 0; i < count; i++)
  75.     length += strlen (optionvec[i]) + 1;
  76.  
  77.   result = (char *) xmalloc (length + 1);
  78.   result[0] = 0;
  79.  
  80.   for (i = 0; i < count; i++)
  81.     {
  82.       strcat (result, " ");
  83.       strcat (result, optionvec[i]);
  84.     }
  85.  
  86.   return result;
  87. }
  88.  
  89. /* Convert STR to a positive integer, storing the result in *OUT. 
  90.    If STR is not a valid integer, return -1 (otherwise 0). */
  91. static int
  92. ck_atoi (str, out)
  93.      char *str;
  94.      int *out;
  95. {
  96.   char *p;
  97.   for (p = str; *p; p++)
  98.     if (*p < '0' || *p > '9')
  99.       return -1;
  100.  
  101.   *out = atoi (optarg);
  102.   return 0;
  103. }
  104.  
  105. /* Keep track of excluded file name patterns.  */
  106.  
  107. static const char **exclude;
  108. static int exclude_alloc, exclude_count;
  109.  
  110. int
  111. excluded_filename (f)
  112.      const char *f;
  113. {
  114.   int i;
  115.   for (i = 0;  i < exclude_count;  i++)
  116.     if (fnmatch (exclude[i], f, 0) == 0)
  117.       return 1;
  118.   return 0;
  119. }
  120.  
  121. static void
  122. add_exclude (pattern)
  123.      const char *pattern;
  124. {
  125.   if (exclude_alloc <= exclude_count)
  126.     exclude = (const char **)
  127.           (exclude_alloc == 0
  128.            ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
  129.            : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
  130.  
  131.   exclude[exclude_count++] = pattern;
  132. }
  133.  
  134. static int
  135. add_exclude_file (name)
  136.      const char *name;
  137. {
  138.   struct file_data f;
  139.   char *p, *q, *lim;
  140.  
  141.   f.name = optarg;
  142.   f.desc = strcmp (optarg, "-") == 0 ? 0 : open (optarg, O_RDONLY, 0);
  143.   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
  144.     return -1;
  145.  
  146.   sip (&f, 1);
  147.   slurp (&f);
  148.  
  149.   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
  150.     {
  151.       q = memchr (p, '\n', lim - p);
  152.       if (!q)
  153.     q = lim;
  154.       *q++ = 0;
  155.       add_exclude (p);
  156.     }
  157.  
  158.   return close (f.desc);
  159. }
  160.  
  161. /* The numbers 129- that appear in the fourth element of some entries
  162.    tell the big switch in `main' how to process those options.  */
  163.  
  164. static struct option longopts[] =
  165. {
  166.   {"ignore-blank-lines", 0, 0, 'B'},
  167.   {"context", 2, 0, 'C'},
  168.   {"ifdef", 1, 0, 'D'},
  169.   {"show-function-line", 1, 0, 'F'},
  170.   {"speed-large-files", 0, 0, 'H'},
  171.   {"ignore-matching-lines", 1, 0, 'I'},
  172.   {"label", 1, 0, 'L'},
  173.   {"file-label", 1, 0, 'L'},    /* An alias, no longer recommended */
  174.   {"new-file", 0, 0, 'N'},
  175.   {"entire-new-file", 0, 0, 'N'},    /* An alias, no longer recommended */
  176.   {"unidirectional-new-file", 0, 0, 'P'},
  177.   {"starting-file", 1, 0, 'S'},
  178.   {"initial-tab", 0, 0, 'T'},
  179.   {"width", 1, 0, 'W'},
  180.   {"text", 0, 0, 'a'},
  181.   {"ascii", 0, 0, 'a'},        /* An alias, no longer recommended */
  182.   {"ignore-space-change", 0, 0, 'b'},
  183.   {"minimal", 0, 0, 'd'},
  184.   {"ed", 0, 0, 'e'},
  185.   {"forward-ed", 0, 0, 'f'},
  186.   {"ignore-case", 0, 0, 'i'},
  187.   {"paginate", 0, 0, 'l'},
  188.   {"print", 0, 0, 'l'},        /* An alias, no longer recommended */
  189.   {"rcs", 0, 0, 'n'},
  190.   {"show-c-function", 0, 0, 'p'},
  191.   {"binary", 0, 0, 'q'},    /* An alias, no longer recommended */
  192.   {"brief", 0, 0, 'q'},
  193.   {"recursive", 0, 0, 'r'},
  194.   {"report-identical-files", 0, 0, 's'},
  195.   {"expand-tabs", 0, 0, 't'},
  196.   {"version", 0, 0, 'v'},
  197.   {"ignore-all-space", 0, 0, 'w'},
  198.   {"exclude", 1, 0, 'x'},
  199.   {"exclude-from", 1, 0, 'X'},
  200.   {"side-by-side", 0, 0, 'y'},
  201.   {"unified", 2, 0, 'U'},
  202.   {"left-column", 0, 0, 129},
  203.   {"suppress-common-lines", 0, 0, 130},
  204.   {"sdiff-merge-assist", 0, 0, 131},
  205.   {"old-line-format", 1, 0, 132},
  206.   {"new-line-format", 1, 0, 133},
  207.   {"unchanged-line-format", 1, 0, 134},
  208.   {"old-group-format", 1, 0, 135},
  209.   {"new-group-format", 1, 0, 136},
  210.   {"unchanged-group-format", 1, 0, 137},
  211.   {"changed-group-format", 1, 0, 138},
  212.   {0, 0, 0, 0}
  213. };
  214.  
  215. int
  216. main (argc, argv)
  217.      int argc;
  218.      char *argv[];
  219. {
  220.   int val;
  221.   int c;
  222.   int prev = -1;
  223.   extern char *version_string;
  224.   int width = DEFAULT_WIDTH;
  225.  
  226.   program = argv[0];
  227.  
  228.   /* Do our initializations. */
  229.   output_style = OUTPUT_NORMAL;
  230.   always_text_flag = FALSE;
  231.   ignore_space_change_flag = FALSE;
  232.   ignore_all_space_flag = FALSE;
  233.   length_varies = FALSE;
  234.   ignore_case_flag = FALSE;
  235.   ignore_blank_lines_flag = FALSE;
  236.   ignore_regexp_list = NULL;
  237.   function_regexp_list = NULL;
  238.   print_file_same_flag = FALSE;
  239.   entire_new_file_flag = FALSE;
  240.   unidirectional_new_file_flag = FALSE;
  241.   no_details_flag = FALSE;
  242.   context = -1;
  243.   line_end_char = '\n';
  244.   tab_align_flag = FALSE;
  245.   tab_expand_flag = FALSE;
  246.   recursive = FALSE;
  247.   paginate_flag = FALSE;
  248.   heuristic = FALSE;
  249.   dir_start_file = NULL;
  250.   msg_chain = NULL;
  251.   msg_chain_end = NULL;
  252.   no_discards = 0;
  253.  
  254.   /* Decode the options.  */
  255.  
  256.   while ((c = getopt_long (argc, argv,
  257.                "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
  258.                longopts, (int *)0)) != EOF)
  259.     {
  260.       switch (c)
  261.     {
  262.       /* All digits combine in decimal to specify the context-size.  */
  263.     case '1':
  264.     case '2':
  265.     case '3':
  266.     case '4':
  267.     case '5':
  268.     case '6':
  269.     case '7':
  270.     case '8':
  271.     case '9':
  272.     case '0':
  273.       if (context == -1)
  274.         context = 0;
  275.       /* If a context length has already been specified,
  276.          more digits allowed only if they follow right after the others.
  277.          Reject two separate runs of digits, or digits after -C.  */
  278.       else if (prev < '0' || prev > '9')
  279.         fatal ("context length specified twice");
  280.  
  281.       context = context * 10 + c - '0';
  282.       break;
  283.  
  284.     case 'a':
  285.       /* Treat all files as text files; never treat as binary.  */
  286.       always_text_flag = 1;
  287.       break;
  288.  
  289.     case 'b':
  290.       /* Ignore changes in amount of whitespace.  */
  291.       ignore_space_change_flag = 1;
  292.       length_varies = 1;
  293.       break;
  294.  
  295.     case 'B':
  296.       /* Ignore changes affecting only blank lines.  */
  297.       ignore_blank_lines_flag = 1;
  298.       break;
  299.  
  300.     case 'C':        /* +context[=lines] */
  301.     case 'U':        /* +unified[=lines] */
  302.       if (optarg)
  303.         {
  304.           if (context >= 0)
  305.         fatal ("context length specified twice");
  306.  
  307.           if (ck_atoi (optarg, &context))
  308.         fatal ("invalid context length argument");
  309.         }
  310.  
  311.       /* Falls through.  */
  312.     case 'c':
  313.       /* Make context-style output.  */
  314.       specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
  315.       break;
  316.  
  317.     case 'd':
  318.       /* Don't discard lines.  This makes things slower (sometimes much
  319.          slower) but will find a guaranteed minimal set of changes.  */
  320.       no_discards = 1;
  321.       break;
  322.  
  323.     case 'D':
  324.       /* Make merged #ifdef output.  */
  325.       specify_style (OUTPUT_IFDEF);
  326.       {
  327.         int i, err = 0;
  328.         static const char C_ifdef_group_formats[] =
  329.           "#ifndef %s\n%%<#endif /* not %s */\n%c#ifdef %s\n%%>#endif /* %s */\n%c%%=%c#ifndef %s\n%%<#else /* %s */\n%%>#endif /* %s */\n";
  330.         char *b = xmalloc (sizeof (C_ifdef_group_formats)
  331.                    + 7 * strlen(optarg) - 14 /* 7*"%s" */
  332.                    - 8 /* 5*"%%" + 3*"%c" */);
  333.         sprintf (b, C_ifdef_group_formats,
  334.              optarg, optarg, 0,
  335.              optarg, optarg, 0, 0,
  336.              optarg, optarg, optarg);
  337.         for (i = 0; i < 4; i++)
  338.           {
  339.         err |= specify_format (&group_format[i], b);
  340.         b += strlen (b) + 1;
  341.           }
  342.         if (err)
  343.           error ("conflicting #ifdef formats", 0, 0);
  344.       }
  345.       break;
  346.  
  347.     case 'e':
  348.       /* Make output that is a valid `ed' script.  */
  349.       specify_style (OUTPUT_ED);
  350.       break;
  351.  
  352.     case 'f':
  353.       /* Make output that looks vaguely like an `ed' script
  354.          but has changes in the order they appear in the file.  */
  355.       specify_style (OUTPUT_FORWARD_ED);
  356.       break;
  357.  
  358.     case 'F':
  359.       /* Show, for each set of changes, the previous line that
  360.          matches the specified regexp.  Currently affects only
  361.          context-style output.  */
  362.       add_regexp (&function_regexp_list, optarg);
  363.       break;
  364.  
  365.     case 'h':
  366.       /* Split the files into chunks of around 1500 lines
  367.          for faster processing.  Usually does not change the result.
  368.  
  369.          This currently has no effect.  */
  370.       break;
  371.  
  372.     case 'H':
  373.       /* Turn on heuristics that speed processing of large files
  374.          with a small density of changes.  */
  375.       heuristic = 1;
  376.       break;
  377.  
  378.     case 'i':
  379.       /* Ignore changes in case.  */
  380.       ignore_case_flag = 1;
  381.       break;
  382.  
  383.     case 'I':
  384.       /* Ignore changes affecting only lines that match the
  385.          specified regexp.  */
  386.       add_regexp (&ignore_regexp_list, optarg);
  387.       break;
  388.  
  389.     case 'l':
  390.       /* Pass the output through `pr' to paginate it.  */
  391.       paginate_flag = 1;
  392.       break;
  393.  
  394.     case 'L':
  395.       /* Specify file labels for `-c' output headers.  */
  396.       if (!file_label[0])
  397.         file_label[0] = optarg;
  398.       else if (!file_label[1])
  399.         file_label[1] = optarg;
  400.       else
  401.         fatal ("too many file label options");
  402.       break;
  403.       
  404.     case 'n':
  405.       /* Output RCS-style diffs, like `-f' except that each command
  406.          specifies the number of lines affected.  */
  407.       specify_style (OUTPUT_RCS);
  408.       break;
  409.  
  410.     case 'N':
  411.       /* When comparing directories, if a file appears only in one
  412.          directory, treat it as present but empty in the other.  */
  413.       entire_new_file_flag = 1;
  414.       break;
  415.  
  416.     case 'p':
  417.       /* Make context-style output and show name of last C function.  */
  418.       specify_style (OUTPUT_CONTEXT);
  419.       add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
  420.       break;
  421.  
  422.     case 'P':
  423.       /* When comparing directories, if a file appears only in
  424.          the second directory of the two,
  425.          treat it as present but empty in the other.  */
  426.       unidirectional_new_file_flag = 1;
  427.       break;
  428.  
  429.     case 'q':
  430.       no_details_flag = 1;
  431.       break;
  432.  
  433.     case 'r':
  434.       /* When comparing directories, 
  435.          recursively compare any subdirectories found.  */
  436.       recursive = 1;
  437.       break;
  438.  
  439.     case 's':
  440.       /* Print a message if the files are the same.  */
  441.       print_file_same_flag = 1;
  442.       break;
  443.  
  444.     case 'S':
  445.       /* When comparing directories, start with the specified
  446.          file name.  This is used for resuming an aborted comparison.  */
  447.       dir_start_file = optarg;
  448.       break;
  449.  
  450.     case 't':
  451.       /* Expand tabs to spaces in the output so that it preserves
  452.          the alignment of the input files.  */
  453.       tab_expand_flag = 1;
  454.       break;
  455.  
  456.     case 'T':
  457.       /* Use a tab in the output, rather than a space, before the
  458.          text of an input line, so as to keep the proper alignment
  459.          in the input line without changing the characters in it.  */
  460.       tab_align_flag = 1;
  461.       break;
  462.  
  463.     case 'u':
  464.       /* Output the context diff in unidiff format.  */
  465.       specify_style (OUTPUT_UNIFIED);
  466.       break;
  467.  
  468.     case 'v':
  469.       fprintf (stderr, "GNU diff version %s\n", version_string);
  470.       break;
  471.  
  472.     case 'w':
  473.       /* Ignore horizontal whitespace when comparing lines.  */
  474.       ignore_all_space_flag = 1;
  475.       length_varies = 1;
  476.       break;
  477.  
  478.     case 'x':
  479.       add_exclude (optarg);
  480.       break;
  481.  
  482.     case 'X':
  483.       if (add_exclude_file (optarg) != 0)
  484.         pfatal_with_name (optarg);
  485.       break;
  486.  
  487.     case 'y':
  488.       /* Use side-by-side (sdiff-style) columnar output. */
  489.       specify_style (OUTPUT_SDIFF);
  490.       break;
  491.  
  492.     case 'W':
  493.       /* Set the line width for OUTPUT_SDIFF.  */
  494.       if (ck_atoi (optarg, &width) || width <= 0)
  495.         fatal ("column width must be a positive integer");
  496.       break;
  497.       
  498.     case 129:
  499.       sdiff_left_only = 1;
  500.       break;
  501.       
  502.     case 130:
  503.       sdiff_skip_common_lines = 1;
  504.       break;
  505.       
  506.     case 131:
  507.       /* sdiff-style columns output. */
  508.       specify_style (OUTPUT_SDIFF);
  509.       sdiff_help_sdiff = 1;
  510.       break;
  511.  
  512.     case 132:
  513.     case 133:
  514.     case 134:
  515.       specify_style (OUTPUT_IFDEF);
  516.       {
  517.         const char **form = &line_format[c - 132];
  518.         if (*form && strcmp (*form, optarg) != 0)
  519.           error ("conflicting line format", 0, 0);
  520.         *form = optarg;
  521.       }
  522.       break;
  523.  
  524.     case 135:
  525.     case 136:
  526.     case 137:
  527.     case 138:
  528.       specify_style (OUTPUT_IFDEF);
  529.       {
  530.         const char **form = &group_format[c - 135];
  531.         if (*form && strcmp (*form, optarg) != 0)
  532.           error ("conflicting group format", 0, 0);
  533.         *form = optarg;
  534.       }
  535.       break;
  536.  
  537.     default:
  538.       usage ();
  539.     }
  540.       prev = c;
  541.     }
  542.  
  543.   if (optind != argc - 2)
  544.     usage ();
  545.  
  546.  
  547.   {
  548.     /*
  549.      *    We maximize first the half line width, and then the gutter width,
  550.      *    according to the following constraints:
  551.      *    1.  Two half lines plus a gutter must fit in a line.
  552.      *    2.  If the half line width is nonzero:
  553.      *        a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
  554.      *        b.  If tabs are not expanded to spaces,
  555.      *        a half line plus a gutter is an integral number of tabs,
  556.      *        so that tabs in the right column line up.
  557.      */
  558.     int t = tab_expand_flag ? 1 : TAB_WIDTH;
  559.     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
  560.     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
  561.     sdiff_column2_offset = sdiff_half_width ? off : width;
  562.   }
  563.  
  564.   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
  565.     context = 0;
  566.   else if (context == -1)
  567.     /* Default amount of context for -c.  */
  568.     context = 3;
  569.  
  570.   if (output_style == OUTPUT_IFDEF)
  571.     {
  572.       int i;
  573.       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
  574.     if (!line_format[i])
  575.       line_format[i] = "%l\n";
  576.       if (!group_format[OLD])
  577.     group_format[OLD]
  578.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
  579.       if (!group_format[NEW])
  580.     group_format[NEW]
  581.       = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
  582.       if (!group_format[UNCHANGED])
  583.     group_format[UNCHANGED] = "%=";
  584.       if (!group_format[CHANGED])
  585.     group_format[CHANGED] = concat (group_format[OLD],
  586.                     group_format[NEW], "");
  587.     }
  588.  
  589.   no_diff_means_no_output =
  590.     (output_style == OUTPUT_IFDEF ?
  591.       (!*group_format[UNCHANGED]
  592.        || (strcmp (group_format[UNCHANGED], "%=") == 0
  593.        && !*line_format[UNCHANGED]))
  594.      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
  595.  
  596.   switch_string = option_list (argv + 1, optind - 1);
  597.  
  598.   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
  599.  
  600.   /* Print any messages that were saved up for last.  */
  601.   print_message_queue ();
  602.  
  603.   if (ferror (stdout) || fclose (stdout) != 0)
  604.     fatal ("write error");
  605.   exit (val);
  606.   return val;
  607. }
  608.  
  609. /* Add the compiled form of regexp PATTERN to REGLIST.  */
  610.  
  611. static void
  612. add_regexp (reglist, pattern)
  613.      struct regexp_list **reglist;
  614.      char *pattern;
  615. {
  616.   struct regexp_list *r;
  617.   const char *m;
  618.  
  619.   r = (struct regexp_list *) xmalloc (sizeof (*r));
  620.   bzero (r, sizeof (*r));
  621.   r->buf.fastmap = (char *) xmalloc (256);
  622.   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
  623.   if (m != 0)
  624.     error ("%s: %s", pattern, m);
  625.  
  626.   /* Add to the start of the list, since it's easier than the end.  */
  627.   r->next = *reglist;
  628.   *reglist = r;
  629. }
  630.  
  631. static void
  632. usage ()
  633. {
  634.   fprintf (stderr, "Usage: %s [options] from-file to-file\n", program);
  635.   fprintf (stderr, "Options:\n\
  636.        [-abBcdefhHilnNpPqrstTuvwy] [-C lines] [-D name] [-F regexp]\n\
  637.        [-I regexp] [-L from-label [-L to-label]] [-S starting-file] [-U lines]\n\
  638.        [-W columns] [-x pattern] [-X pattern-file] [--exclude=pattern]\n\
  639.        [--exclude-from=pattern-file] [--ignore-blank-lines] [--context[=lines]]\n\
  640.        [--ifdef=name] [--show-function-line=regexp] [--speed-large-files]\n\
  641.        [--label=from-label [--label=to-label]] [--new-file]\n");
  642.   fprintf (stderr, "\
  643.        [--ignore-matching-lines=regexp] [--unidirectional-new-file]\n\
  644.        [--starting-file=starting-file] [--initial-tab] [--width=columns]\n\
  645.        [--text] [--ignore-space-change] [--minimal] [--ed] [--forward-ed]\n\
  646.        [--ignore-case] [--paginate] [--rcs] [--show-c-function] [--brief]\n\
  647.        [--recursive] [--report-identical-files] [--expand-tabs] [--version]\n");
  648.   fprintf (stderr, "\
  649.        [--ignore-all-space] [--side-by-side] [--unified[=lines]]\n\
  650.        [--left-column] [--suppress-common-lines] [--sdiff-merge-assist]\n\
  651.        [--old-line-format=format] [--new-line-format=format]\n\
  652.        [--unchanged-line-format=format]\n\
  653.        [--old-group-format=format] [--new-group-format=format]\n\
  654.        [--unchanged-group-format=format] [--changed-group-format=format]\n");
  655.   exit (2);
  656.  
  657. static int
  658. specify_format (var, value)
  659.      const char **var;
  660.      const char *value;
  661. {
  662.   int err = *var ? strcmp (*var, value) : 0;
  663.   *var = value;
  664.   return err;
  665. }
  666.  
  667. static void
  668. specify_style (style)
  669.      enum output_style style;
  670. {
  671.   if (output_style != OUTPUT_NORMAL
  672.       && output_style != style)
  673.     error ("conflicting specifications of output style", 0, 0);
  674.   output_style = style;
  675. }
  676.  
  677. /* Compare two files (or dirs) with specified names
  678.    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
  679.    (if DIR0 is 0, then the name is just NAME0, etc.)
  680.    This is self-contained; it opens the files and closes them.
  681.  
  682.    Value is 0 if files are the same, 1 if different,
  683.    2 if there is a problem opening them.  */
  684.  
  685. static int
  686. compare_files (dir0, name0, dir1, name1, depth)
  687.      char *dir0, *dir1;
  688.      char *name0, *name1;
  689.      int depth;
  690. {
  691.   struct file_data inf[2];
  692.   register int i;
  693.   int val;
  694.   int same_files;
  695.   int errorcount = 0;
  696.  
  697.   /* If this is directory comparison, perhaps we have a file
  698.      that exists only in one of the directories.
  699.      If so, just print a message to that effect.  */
  700.  
  701.   if (! ((name0 != 0 && name1 != 0)
  702.      || (unidirectional_new_file_flag && name1 != 0)
  703.      || entire_new_file_flag))
  704.     {
  705.       char *name = name0 == 0 ? name1 : name0;
  706.       char *dir = name0 == 0 ? dir1 : dir0;
  707.       message ("Only in %s: %s\n", dir, name);
  708.       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
  709.       return 1;
  710.     }
  711.  
  712.   /* Mark any nonexistent file with -1 in the desc field.  */
  713.   /* Mark unopened files (i.e. directories) with -2. */
  714.  
  715.   inf[0].desc = name0 == 0 ? -1 : -2;
  716.   inf[1].desc = name1 == 0 ? -1 : -2;
  717.  
  718.   /* Now record the full name of each file, including nonexistent ones.  */
  719.  
  720.   if (name0 == 0)
  721.     name0 = name1;
  722.   if (name1 == 0)
  723.     name1 = name0;
  724.  
  725. #ifndef AMIGA
  726.   inf[0].name = dir0 == 0 ? name0 : concat (dir0, "/", name0);
  727.   inf[1].name = dir1 == 0 ? name1 : concat (dir1, "/", name1);
  728. #else /* AMIGA */
  729.   {
  730.     int len = strlen (dir0);
  731.     if (dir0 == 0 || len == 0)
  732.       inf[0].name = name0;
  733.     else if (dir0[len-1] == ':')
  734.       inf[0].name = concat (dir0, "", name0);
  735.     else
  736.       inf[0].name = concat (dir0, "/", name0);
  737.   }
  738.   {
  739.     int len = strlen (dir1);
  740.     if (dir1 == 0 || len == 0)
  741.       inf[1].name = name1;
  742.     else if (dir1[len-1] == ':')
  743.       inf[1].name = concat (dir1, "", name1);
  744.     else
  745.       inf[1].name = concat (dir1, "/", name1);
  746.   }
  747. #endif /* AMIGA */
  748.  
  749.   /* Stat the files.  Record whether they are directories.  */
  750.  
  751.   for (i = 0; i <= 1; i++)
  752.     {
  753.       bzero (&inf[i].stat, sizeof (struct stat));
  754.       inf[i].dir_p = 0;
  755.  
  756.       if (inf[i].desc != -1)
  757.     {
  758.       int stat_result;
  759.  
  760.       if (strcmp (inf[i].name, "-") == 0)
  761.         {
  762.           inf[i].desc = 0;
  763.           inf[i].name = "Standard Input";
  764. #ifndef AMIGA
  765.           stat_result = fstat (0, &inf[i].stat);
  766. #else /* AMIGA */
  767.           stat_result = fake_stat_result (&inf[i].stat);
  768. #endif /* AMIGA */
  769.         }
  770.       else
  771.         stat_result = stat (inf[i].name, &inf[i].stat);
  772.  
  773.       if (stat_result != 0)
  774.         {
  775.           perror_with_name (inf[i].name);
  776.           errorcount = 1;
  777.         }
  778.       else
  779.         inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
  780.     }
  781.     }
  782.  
  783.   if (name0 == 0)
  784.     inf[0].dir_p = inf[1].dir_p;
  785.   if (name1 == 0)
  786.     inf[1].dir_p = inf[0].dir_p;
  787.  
  788.   if (errorcount == 0 && depth == 0 && inf[0].dir_p != inf[1].dir_p)
  789.     {
  790.       /* If one is a directory, and it was specified in the command line,
  791.      use the file in that dir with the other file's basename.  */
  792.  
  793. #ifndef AMIGA
  794.       int fnm_arg = inf[0].dir_p;
  795.       int dir_arg = 1 - fnm_arg;
  796.       char *p = rindex (inf[fnm_arg].name, '/');
  797.       char *filename = inf[dir_arg].name
  798.     = concat (inf[dir_arg].name,  "/", (p ? p+1 : inf[fnm_arg].name));
  799. #else /* AMIGA */
  800.       int fnm_arg, dir_arg;
  801.       char *p1, *p2, *p;
  802.       char *filename;
  803.  
  804.       fnm_arg = inf[0].dir_p;
  805.       dir_arg = 1 - fnm_arg;
  806.       p1 = rindex (inf[fnm_arg].name, '/');
  807.       p2 = rindex (inf[fnm_arg].name, ':');
  808.       p = max (p1, p2);
  809.       if (*(inf[dir_arg].name + strlen (inf[dir_arg].name) - 1) == ':')
  810.         filename = inf[dir_arg].name
  811.           = concat (inf[dir_arg].name, "", (p ? p+1 : inf[fnm_arg].name));
  812.       else
  813.         filename = inf[dir_arg].name
  814.           = concat (inf[dir_arg].name, "/", (p ? p+1 : inf[fnm_arg].name));
  815. #endif /* !AMIGA */
  816.  
  817.       if (inf[fnm_arg].desc == 0)
  818.     fatal ("can't compare - to a directory");
  819.  
  820.       if (stat (filename, &inf[dir_arg].stat) != 0)
  821.     {
  822.       perror_with_name (filename);
  823.       errorcount = 1;
  824.     }
  825.       else
  826.     inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
  827.     }
  828.  
  829.   if (errorcount)
  830.     {
  831.  
  832.       /* If either file should exist but does not, return 2.  */
  833.  
  834.       val = 2;
  835.  
  836.     }
  837.   else if ((same_files =    inf[0].stat.st_ino == inf[1].stat.st_ino
  838.              && inf[0].stat.st_dev == inf[1].stat.st_dev
  839.              && inf[0].desc != -1
  840.              && inf[1].desc != -1)
  841.        && no_diff_means_no_output)
  842.     {
  843.       /* The two named files are actually the same physical file.
  844.      We know they are identical without actually reading them.  */
  845.  
  846.       val = 0;
  847.     }
  848.   else if (inf[0].dir_p & inf[1].dir_p)
  849.     {
  850.       if (output_style == OUTPUT_IFDEF)
  851.     fatal ("-D option not supported with directories");
  852.  
  853.       /* If both are directories, compare the files in them.  */
  854.  
  855.       if (depth > 0 && !recursive)
  856.     {
  857.       /* But don't compare dir contents one level down
  858.          unless -r was specified.  */
  859.       message ("Common subdirectories: %s and %s\n",
  860.            inf[0].name, inf[1].name);
  861.       val = 0;
  862.     }
  863.       else
  864.     {
  865.       val = diff_dirs (inf, compare_files, depth);
  866.     }
  867.  
  868.     }
  869.   else if (inf[0].dir_p | inf[1].dir_p)
  870.     {
  871.       /* Perhaps we have a subdirectory that exists only in one directory.
  872.      If so, just print a message to that effect.  */
  873.  
  874.       if (inf[0].desc == -1 || inf[1].desc == -1)
  875.     {
  876.       if (recursive
  877.           && (entire_new_file_flag
  878.           || (unidirectional_new_file_flag && inf[0].desc == -1)))
  879.         val = diff_dirs (inf, compare_files, depth);
  880.       else
  881.         {
  882.           char *dir = (inf[0].desc == -1) ? dir1 : dir0;
  883.           message ("Only in %s: %s\n", dir, name0);
  884.           val = 1;
  885.         }
  886.     }
  887.       else
  888.     {
  889.       /* We have a subdirectory in one directory
  890.          and a file in the other.  */
  891.  
  892.       message ("%s is a directory but %s is not\n",
  893.            inf[1 - inf[0].dir_p].name, inf[inf[0].dir_p].name);
  894.  
  895.       /* This is a difference.  */
  896.       val = 1;
  897.     }
  898.     }
  899.   else if (no_details_flag
  900.        && inf[0].stat.st_size != inf[1].stat.st_size
  901.        && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
  902.        && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
  903.     {
  904.       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
  905.       val = 1;
  906.     }
  907.   else
  908.     {
  909.       /* Both exist and neither is a directory.  */
  910.  
  911.       /* Open the files and record their descriptors.  */
  912.  
  913.       if (inf[0].desc == -2)
  914.     if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
  915.       {
  916.         perror_with_name (inf[0].name);
  917.         errorcount = 1;
  918.       }
  919.       if (inf[1].desc == -2)
  920.     if (same_files)
  921.       inf[1].desc = inf[0].desc;
  922.     else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
  923.       {
  924.         perror_with_name (inf[1].name);
  925.         errorcount = 1;
  926.       }
  927.     
  928.       /* Compare the files, if no error was found.  */
  929.  
  930.       val = errorcount ? 2 : diff_2_files (inf, depth);
  931.  
  932.       /* Close the file descriptors.  */
  933.  
  934.       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
  935.     {
  936.       perror_with_name (inf[0].name);
  937.       val = 2;
  938.     }
  939.       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
  940.       && close (inf[1].desc) != 0)
  941.     {
  942.       perror_with_name (inf[1].name);
  943.       val = 2;
  944.     }
  945.     }
  946.  
  947.   /* Now the comparison has been done, if no error prevented it,
  948.      and VAL is the value this function will return.  */
  949.  
  950.   if (val == 0 && !inf[0].dir_p)
  951.     {
  952.       if (print_file_same_flag)
  953.     message ("Files %s and %s are identical\n",
  954.          inf[0].name, inf[1].name);
  955.     }
  956.   else
  957.     fflush (stdout);
  958.  
  959. #ifndef AMIGA
  960.   if (dir0 != 0)
  961.     free (inf[0].name);
  962.   if (dir1 != 0)
  963.     free (inf[1].name);
  964. #else /* AMIGA */
  965.   if (dir0 != 0 && strlen(dir0) != 0)
  966.     free (inf[0].name);
  967.   if (dir1 != 0 && strlen(dir1) != 0)
  968.     free (inf[1].name);
  969. #endif /* !AMIGA */
  970.  
  971.   return val;
  972. }
  973.  
  974. #ifdef AMIGA
  975. static int fake_stat_result (sbuf)
  976.      struct stat *sbuf;
  977. {
  978.   time_t cur_time;
  979.  
  980.   time (&cur_time);
  981.   sbuf->st_dev = 0;
  982.   sbuf->st_ino = 0;
  983.   sbuf->st_mode = S_IREAD;
  984.   sbuf->st_nlink = 1;
  985.   sbuf->st_uid = 0;
  986.   sbuf->st_gid = 0;
  987.   sbuf->st_size = 0;
  988.   sbuf->st_ctime = sbuf->st_atime = sbuf->st_mtime = cur_time;
  989.   return 0;
  990. }
  991. #endif /* AMIGA */
  992.